package pl.enigmatic.parser.xml;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Vector;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

import pl.enigmatic.util.Reflection;
import pl.enigmatic.util.Strings;


public class XMLNSRetriever {

	/** the prefix to use for package extension namespace declaration */
	public static final String XMLNS_PACKAGE_PREFIX = Parser.XMLNS("package");
	public static final String DIRECT_SUBPACKAGES = ".*";
	public static final String DIRECT_SUBPACKAGES_AND_ME = ".**";
	public static final String ALL_SUBPACKAGES = ".+";
	public static final String ALL_SUBPACKAGES_AND_ME = ".++";

	/**
	 * Retrieves the package from the given namespace
	 * @param xmlns namespace to extract package from
	 * @return package associated with the given namespace
	 */
	private static List<String> parse(final String xmlns) {
		final List<String> list = new Vector<String>();
		if (xmlns != null) {
			for (final String string : Strings.trimAllWhitespaces(xmlns.substring(XMLNS_PACKAGE_PREFIX.length())).replace('/', '.').split(" ")) {
				if (string.endsWith(DIRECT_SUBPACKAGES)) {
					list.addAll(Reflection.getDirectSubpackages(string.substring(0, string.length() - DIRECT_SUBPACKAGES.length())));
				} else if (string.endsWith(DIRECT_SUBPACKAGES_AND_ME)) {
					final String me = string.substring(0, string.length() - DIRECT_SUBPACKAGES_AND_ME.length());
					list.add(me);
					list.addAll(Reflection.getDirectSubpackages(me));
				} else if (string.endsWith(ALL_SUBPACKAGES)) {
					list.addAll(Reflection.getAllSubpackages(string.substring(0, string.length() - DIRECT_SUBPACKAGES.length())));
				} else if (string.endsWith(ALL_SUBPACKAGES_AND_ME)) {
					final String me = string.substring(0, string.length() - DIRECT_SUBPACKAGES_AND_ME.length());
					list.add(me);
					list.addAll(Reflection.getAllSubpackages(me));
				} else {
					list.add(string);
				}
			}
		}
		return list;
	}

	/**
	 * Removes all namespaces attributes from the document and puts them into user data for future uses
	 * @param doc document to be cleaned
	 * @return document element with cleaned namespaces attributes
	 */
	public static <R extends XMLNSRetriever> R clean(final R target, final Document doc) {
		final Map<String, List<String>> packages = target.packages;
		packages.put(null, parse(doc.lookupNamespaceURI(null)));
		packages.get(null).add("java.lang");
		final Element xml = doc.getDocumentElement();
		final String xmlns = "xmlns";
		xml.removeAttribute(xmlns);
		final NamedNodeMap attr = xml.getAttributes();
		final HashSet<String> remove = new HashSet<String>();
		for (int i = 0; i < attr.getLength(); i++) {
			final Node item = attr.item(i);
			if (xmlns.equals(item.getPrefix())) {
				remove.add(item.getNodeName());
				// FIXME coś nie tak z tym warunkiem
				if (xmlns.startsWith(XMLNS_PACKAGE_PREFIX)) {
					packages.put(item.getNodeName().split(":")[1], parse(item.getNodeValue()));
				}
			}
		}
		for (final String item : remove) {
			attr.removeNamedItem(item);
		}
		return target;
	}

	final Map<String, List<String>> packages = new HashMap<String, List<String>>();

	protected XMLNSRetriever() {}

	public List<String> get(final String prefix) {
		return packages.get(prefix);
	}

	public void add(final String prefix, final String packageName) {
		final List<String> list;
		if (!packages.containsKey(prefix)) {
			packages.put(prefix, list = new Vector<String>());
		} else {
			list = packages.get(prefix);
		}
		if (!list.contains(packageName)) {
			list.add(packageName);
		}
	}

	public void addAll(final String prefix, final List<String> packageNames) {
		for (final String p : packageNames) {
			add(prefix, p);
		}
	}

	public void add(final String packageName) {
		add(null, packageName);
	}

	public void addAll(final List<String> packageNames) {
		for (final String p : packageNames) {
			add(p);
		}
	}

	public XMLNSRetriever copy(final List<String> add) {
		final XMLNSRetriever res = new XMLNSRetriever();
		for (final Entry<String, List<String>> e : packages.entrySet()) {
			res.addAll(e.getKey(), e.getValue());
		}
		res.addAll(add);
		return res;
	}
}